/*
 * COPYRIGHT. ShenZhen JiMi Technology Co., Ltd. 2017.
 * ALL RIGHTS RESERVED.
 *
 * No part of this publication may be reproduced, stored in a retrieval system, or transmitted,
 * on any form or by any means, electronic, mechanical, photocopying, recording, 
 * or otherwise, without the prior written permission of ShenZhen JiMi Network Technology Co., Ltd.
 *
 * Amendment History:
 * 
 * Date                   By              Description
 * -------------------    -----------     -------------------------------------------
 * 2017年4月6日    li.shangzhi         Create the class
 * http://www.jimilab.com/
 */

package com.jimi.gateway.utils;

import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.jimi.app.api.IApiAppInfoApi;
import com.jimi.app.api.IApiOpenFunctionsApi;
import com.jimi.app.dto.output.ApiAppInfoOutputDto;
import com.jimi.app.dto.output.ApiOpenFunctionsOutputDto;
import com.jimi.commons.DatetimeUtil;
import com.jimi.framework.context.SpringContextHolder;
import com.jimi.gateway.exception.GatewayErrorCode;
import com.netflix.zuul.context.RequestContext;

/**
 * @FileName ValidatorUtils.java
 * @Description: 参数校验工具类
 *
 * @Date 2017年4月6日 下午5:49:23
 * @author li.shangzhi
 * @version 1.0
 */
public class ValidatorUtils {

	private static final Logger logger = LoggerFactory.getLogger(ValidatorUtils.class);

	/**
	 * @Title: checkMethod
	 * @Description: 检查方法名是否存在，合法
	 * @param ctx
	 *            RequestContext上下文
	 * @param values
	 *            参数值
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月6日 下午5:55:44
	 */
	public static boolean checkMethod(RequestContext ctx, String... values) {
		if (null == values || values.length == 0) {
			logger.info("【通用参数校验】没有找到method参数");
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_METHOD_ERROR)));// 返回错误内容
			return false;
		}

		// 检查方法名是否存在
		String method = values[0];
		IApiOpenFunctionsApi openFunctionsApi = SpringContextHolder.getBean(IApiOpenFunctionsApi.class);
		ApiOpenFunctionsOutputDto outputDto = openFunctionsApi.getByMethod(method);
		if (null != outputDto) {
			ctx.set(Constants.OPEN_FUNCTION, outputDto);
			return true;
		}
		logger.info("【通用参数校验】method参数【{}】不存在", method);
		ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_METHOD_ERROR)));// 返回错误内容
		return false;
	}

	/**
	 * @Title: checkAppKey
	 * @Description: 检查AppKey是否存在，合法
	 * @param ctx
	 *            RequestContext上下文
	 * @param values
	 *            参数值
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月6日 下午5:55:44
	 */
	public static boolean checkAppKey(RequestContext ctx, String... values) {
		if (null == values || values.length == 0) {
			logger.info("【通用参数校验】没有找到app_key参数");
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_APPKEY_ERROR)));// 返回错误内容
			return false;
		}

		// 检查App_Key是否合法
		String appKey = values[0];
		IApiAppInfoApi appInfoApi = SpringContextHolder.getBean(IApiAppInfoApi.class);
		ApiAppInfoOutputDto outputDto = appInfoApi.getByAppKey(appKey);
		if (null == outputDto) {
			logger.info("【通用参数校验】无效的app_key:{}", appKey);
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_APPKEY_ERROR)));// 返回错误内容
			return false;
		} else {
			ctx.put(Constants.APP_INFO, outputDto);
		}
		return true;
	}

	/**
	 * @Title: checkTimestamp
	 * @Description: 检查Timestamp是否存在，合法，与服务器时间只能有10分钟误差
	 * @param ctx
	 *            RequestContext上下文
	 * @param values
	 *            参数值
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月6日 下午5:55:44
	 */
	public static boolean checkTimestamp(RequestContext ctx, String... values) {
		if (null == values || values.length == 0) {
			logger.info("【通用参数校验】timestamp时间戳参数不存在");
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_TIMESTAMP_ERROR)));// 返回错误内容
			return false;
		}

		try {
			Date clientDate = DateUtils.parseDate(values[0], "yyyy-MM-dd HH:mm:ss");
			Date serverDate = new Date();
			long timestamp = clientDate.getTime() - serverDate.getTime();
			if (Math.abs(timestamp) > 10 * 60 * 1000) {
				logger.info("【通用参数校验】无效的时间戳，服务器时间:{}，传入时间戳：{}", DatetimeUtil.DateToString(serverDate, "yyyy-MM-dd HH:mm:ss"),
						DatetimeUtil.DateToString(clientDate, "yyyy-MM-dd HH:mm:ss"));
				// 客户端与服务器时间戳误差为10分钟
				ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_TIMESTAMP_ERROR)));// 返回错误内容
				return false;
			}
		} catch (ParseException e) {
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_TIMESTAMP_ERROR)));// 返回错误内容
			return false;
		}
		return true;
	}

	/**
	 * @Title: checkFormat
	 * @Description: 检查Format是否存在，合法
	 * @param ctx
	 *            RequestContext上下文
	 * @param values
	 *            参数值
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月6日 下午5:55:44
	 */
	public static boolean checkFormat(RequestContext ctx, String... values) {
		if (null == values || values.length == 0) {
			logger.info("【通用参数校验】format参数不存在");
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_FORMAT_ERROR)));// 返回错误内容
			return false;
		}
		String format = values[0];
		if (!Constants.JSON.equals(format)) {
			logger.info("【通用参数校验】无效的format参数， foramt：{}", format);
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_FORMAT_ERROR)));// 返回错误内容
			return false;
		}
		return true;
	}

	/**
	 * @Title: checkVersion
	 * @Description: 检查Version是否存在，合法
	 * @param ctx
	 *            RequestContext上下文
	 * @param values
	 *            参数值
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月6日 下午5:55:44
	 */
	public static boolean checkVersion(RequestContext ctx, String... values) {
		if (null == values || values.length == 0) {
			logger.info("【通用参数校验】version参数不存在");
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_VERSION_ERROR)));// 返回错误内容
			return false;
		}

		if (Constants.VERSION_0_9.equals(values[0]) || Constants.VERSION_1_0.equals(values[0])) {
			return true;
		}
		logger.info("【通用参数校验】无效的version参数， versioni：{}", values[0]);
		ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_VERSION_ERROR)));// 返回错误内容
		return false;
	}

	/**
	 * @Title: checkSignMethod
	 * @Description: 检查Sign_Method是否存在，合法
	 * @param ctx
	 *            RequestContext上下文
	 * @param values
	 *            参数值
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月6日 下午5:55:44
	 */
	public static boolean checkSignMethod(RequestContext ctx, String... values) {
		if (null == values || values.length == 0) {
			logger.info("【通用参数校验】sign_method参数不存在");
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_SIGNMETHOD_ERROR)));// 返回错误内容
			return false;
		}
		if (!Constants.SIGN_METHOD_MD5.equals(values[0])) {
			logger.info("【通用参数校验】无效的sign_method参数， sign_method：{}", values[0]);
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_SIGNMETHOD_ERROR)));// 返回错误内容
			return false;
		}
		return true;
	}

	/**
	 * @Title: checkSignMethod
	 * @Description: 检查Sign是否存在，合法
	 * @param ctx
	 *            RequestContext上下文
	 * @param values
	 *            参数值
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月6日 下午5:55:44
	 */
	public static boolean checkSign(RequestContext ctx, String... values) {
		if (null == values || values.length == 0) {
			logger.info("【通用参数校验】sign参数不存在");
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_SIGN_ERROR)));// 返回错误内容
			return false;
		}

		// 计算签名结果
		ApiAppInfoOutputDto outputDto = (ApiAppInfoOutputDto) ctx.get(Constants.APP_INFO);
		String appSecret = outputDto.getAppSecret();

		HttpServletRequest request = ctx.getRequest();
		Map<String, String[]> paramsMap = request.getParameterMap();
		String[] clientSign = paramsMap.get(Constants.SIGN);
		try {
			paramsMap.remove(Constants.SIGN); // 移除签名参数
			String serverSign = SignUtils.signTopRequest(paramsMap, appSecret, Constants.SIGN_METHOD_MD5);
			if (!serverSign.equals(clientSign[0])) {
				logger.info("【通用参数校验】无效的sign参数， 服务器签名：{}，客户端签名：{}", serverSign, clientSign[0]);
				ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_SIGN_ERROR)));// 返回错误内容
				return false;
			}
		} catch (IOException e) {
			logger.error("【通用参数校验】计算签名异常， 请求参数：{}", JSONObject.toJSONString(paramsMap), e);
			ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.PARAM_SIGN_ERROR)));// 返回错误内容
			return false;
		}
		return true;
	}
}
